{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ResNet 残差网络\n",
    "\n",
    "- 参考内容：\n",
    "《动手学深度学习》李沐: \n",
    "https://zh-v2.d2l.ai/chapter_convolutional-modern/resnet.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch.nn import functional as F\n",
    "from d2l import torch as d2l"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.定义残差块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Residual(nn.Module):  \n",
    "    '''\n",
    "    input_channels: 输入通道\n",
    "    num_channels: 输出通道\n",
    "    调用形式：\n",
    "    形式1: 高宽不变，通道数不变   Residual(C, C)\n",
    "    形式2: 高宽减半，通道数翻倍   Residual(C, 2C, use_1x1conv=True, strides=2)\n",
    "    '''\n",
    "    def __init__(self, input_channels, num_channels,\n",
    "                 use_1x1conv=False, strides=1):\n",
    "        super().__init__()\n",
    "        # 定义2个卷积\n",
    "        self.conv1 = nn.Conv2d(input_channels, num_channels,\n",
    "                               kernel_size=3, padding=1, stride=strides)  # (M-3+2)/s + 1 = M/s\n",
    "        self.conv2 = nn.Conv2d(num_channels, num_channels,\n",
    "                               kernel_size=3, padding=1)  # 高宽不变，通道数不变\n",
    "        # 1x1\n",
    "        if use_1x1conv:\n",
    "            self.conv3 = nn.Conv2d(input_channels, num_channels,\n",
    "                                   kernel_size=1, stride=strides)         # 高宽减半，通道数翻倍\n",
    "        else:\n",
    "            self.conv3 = None\n",
    "        \n",
    "        # 2个归一化层\n",
    "        self.bn1 = nn.BatchNorm2d(num_channels)\n",
    "        self.bn2 = nn.BatchNorm2d(num_channels)\n",
    "\n",
    "    # 前向\n",
    "    def forward(self, X):\n",
    "        # 卷积 -> norm -> relu\n",
    "        Y = F.relu(self.bn1(self.conv1(X)))\n",
    "        # 卷积 -> norm \n",
    "        Y = self.bn2(self.conv2(Y))\n",
    "        # 1X1\n",
    "        if self.conv3:\n",
    "            X = self.conv3(X)\n",
    "        # 直连拼接\n",
    "        Y += X\n",
    "        return F.relu(Y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([10, 3, 6, 6])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看输入输出\n",
    "X = torch.rand(10, 3, 6, 6)\n",
    "# 形式1: 高宽不变，通道数不变   Residual(C, C)\n",
    "blk = Residual(3, 3)\n",
    "Y = blk(X)\n",
    "Y.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([10, 6, 3, 3])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 形式2: 高宽减半，通道数翻倍   \n",
    "blk2 = Residual(3, 6, use_1x1conv=True, strides=2)   #10, 6, 3,3\n",
    "Y2 = blk2(X)\n",
    "Y2.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.ResNet网络结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 前3层：高宽变为1/4\n",
    "# 7x7卷积 -> norm -> relu\n",
    "# 池化\n",
    "b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),  # 卷积：步长为2（高宽减半，通道数翻倍）\n",
    "                   nn.BatchNorm2d(64), nn.ReLU(),\n",
    "                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))      # 池化：步长为2（高宽减半）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# resnet块: 2个小的残差块作为一个大块\n",
    "def resnet_block(input_channels, num_channels, num_residuals,\n",
    "                 first_block=False):\n",
    "    '''\n",
    "    num_residuals: 小残差块重复次数  2 \n",
    "    first_block: 是否为第一个resnet块\n",
    "    '''\n",
    "    blk = []\n",
    "    for i in range(num_residuals):\n",
    "        if i == 0 and not first_block:\n",
    "            # 第一个小残差块\n",
    "            # 并且不是第一个resnet块\n",
    "            blk.append(Residual(input_channels, num_channels,\n",
    "                                use_1x1conv=True, strides=2))\n",
    "        else:\n",
    "            blk.append(Residual(num_channels, num_channels))\n",
    "    return blk"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# resnet 18\n",
    "# 1 + 1+ 2*2*4 = 18\n",
    "# 2: 2个小的残差块作为一个大块\n",
    "b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))\n",
    "b3 = nn.Sequential(*resnet_block(64, 128, 2))\n",
    "b4 = nn.Sequential(*resnet_block(128, 256, 2))\n",
    "b5 = nn.Sequential(*resnet_block(256, 512, 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = nn.Sequential(b1, b2, b3, b4, b5,\n",
    "                    nn.AdaptiveAvgPool2d((1,1)),   # 平均汇聚 （512, 1,1）\n",
    "                    nn.Flatten(),          # 展平：1*512\n",
    "                    nn.Linear(512, 10))    # 全连接"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential output shape:\t torch.Size([15, 64, 56, 56])\n",
      "Sequential output shape:\t torch.Size([15, 64, 56, 56])\n",
      "Sequential output shape:\t torch.Size([15, 128, 28, 28])\n",
      "Sequential output shape:\t torch.Size([15, 256, 14, 14])\n",
      "Sequential output shape:\t torch.Size([15, 512, 7, 7])\n",
      "AdaptiveAvgPool2d output shape:\t torch.Size([15, 512, 1, 1])\n",
      "Flatten output shape:\t torch.Size([15, 512])\n",
      "Linear output shape:\t torch.Size([15, 10])\n"
     ]
    }
   ],
   "source": [
    "# 每一层的输出变化\n",
    "X = torch.rand(size=(15, 1, 224, 224))   # 15张图，每张224*224的灰度图\n",
    "for layer in net:\n",
    "    X = layer(X)\n",
    "    print(layer.__class__.__name__,'output shape:\\t', X.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3.模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\nlr, num_epochs, batch_size = 0.05, 5, 256\\ntrain_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)\\nd2l.train_ch6(net, \\n              train_iter, test_iter, \\n              num_epochs, lr, d2l.try_gpu())\\n'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# mnist 手写识别数据 1 \n",
    "# 输出：0-9\n",
    "'''\n",
    "lr, num_epochs, batch_size = 0.05, 5, 256\n",
    "train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)\n",
    "d2l.train_ch6(net, \n",
    "              train_iter, test_iter, \n",
    "              num_epochs, lr, d2l.try_gpu())\n",
    "'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "d2l",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
